using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using log4net;
using Quiki.ExpressionParsers;

namespace Quiki
{
    [SuppressMessage ("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
    public class WikiTextParser
    {
        public WikiTextParser ()
        {
            lineExpressionParsers.Add (new LambdaExpressionParser (
                @"^======(?<text>.+)======$",
                text => new WikiTextElement (WikiTextElementType.Heading, 6, text.Trim ())));
            lineExpressionParsers.Add (new LambdaExpressionParser (
                @"^=====(?<text>.+)=====$", 
                text => new WikiTextElement(WikiTextElementType.Heading, 5, text.Trim())));
            lineExpressionParsers.Add (new LambdaExpressionParser (
                @"^====(?<text>.+)====$",
                text => new WikiTextElement (WikiTextElementType.Heading, 4, text.Trim ())));
            lineExpressionParsers.Add (new LambdaExpressionParser (
                @"^===(?<text>.+)===$",
                text => new WikiTextElement (WikiTextElementType.Heading, 3, text.Trim ())));
            lineExpressionParsers.Add (new LambdaExpressionParser (
                @"^==(?<text>.+)==$",
                text => new WikiTextElement (WikiTextElementType.Heading, 2, text.Trim ())));
            lineExpressionParsers.Add (new LambdaExpressionParser (
                @"^=(?<text>.+)=$",
                text => new WikiTextElement (WikiTextElementType.Heading, 1, text.Trim ())));

            lineExpressionParsers.Add (
                new SimpleLineExpressionParser (
                    @"^(?<text>   .*)", 
                    text => new WikiTextElement(WikiTextElementType.Pre, 1, text.Substring(3)) { Terminal = true }));

            lineExpressionParsers.Add (
                new SimpleLineExpressionParser (
                    @"^(?<text>\t.*)",
                    text => new WikiTextElement (WikiTextElementType.Pre, 1, text.Substring (1)) { Terminal = true }));

            lineExpressionParsers.Add (new SimpleLineExpressionParser (@"^#(?<text>.+)", WikiTextElementType.ListOrdered, 1));
            lineExpressionParsers.Add (new SimpleLineExpressionParser (@"^\*(?<text>.+)", WikiTextElementType.ListUnordered, 1));
            lineExpressionParsers.Add (new SimpleLineExpressionParser (@"^\:(?<text>.+)", WikiTextElementType.ListDefinition, 1));
            lineExpressionParsers.Add (new SimpleLineExpressionParser (@"^\s*$", WikiTextElementType.NewLine));
            lineExpressionParsers.Add (new ParagraphExpressionParser());
            lineExpressionParsers.Add (new FallbackExpressionParser ());

            inlineExpressionParsers.Add (new InternalLinkExpressionParser ());
            inlineExpressionParsers.Add (new ExternalLinkExpressionParser ());
            inlineExpressionParsers.Add (
                new SimpleInlineExpressionParser(
                    @"(?<element>[^\[](?<text>http://\S*))",
                    x =>
                        {
                            WikiTextElement element = new WikiTextElement(WikiTextElementType.ExternalLink);
                            element.Terminal = true;
                            element.Properties["url"] = x;
                            return element;
                        }));
            inlineExpressionParsers.Add (new BinaryWikiTokenInlineExpressionParser("'''", WikiTextElementType.Bold));
            inlineExpressionParsers.Add (new BinaryWikiTokenInlineExpressionParser("''", WikiTextElementType.Italic));
            inlineExpressionParsers.Add (new TemplateExpressionParser());
        }

        public WikiTextElement Parse(string wikiText)
        {
            this.wikiText = wikiText;

            Process();

            return rootElement;
        }

        public static void DumpElement(WikiTextElement element)
        {
            log.Debug(element.ToString());
        }

        private void Process()
        {
            using (reader = new StringReader (wikiText))
            {
                rootElement = new WikiTextElement(WikiTextElementType.Root);

                while (-1 != reader.Peek())
                    ProcessLine();
            }

            ExpandInlineElements(rootElement);
        }

        private void ProcessLine()
        {
            string line = reader.ReadLine();

            foreach (IWikiLineExpressionParser expressionParser in lineExpressionParsers)
            {
                bool processed = expressionParser.Process(line, rootElement);

                if (processed)
                    break;
            }
        }

        private void ExpandInlineElements(WikiTextElement element)
        {
            InlineParse(element);

            foreach (WikiTextElement childElement in element.ChildElements)
                ExpandInlineElements(childElement);
        }

        private void InlineParse(WikiTextElement element)
        {
            foreach (IWikiInlineExpressionParser inlineExpressionParser in inlineExpressionParsers)
                inlineExpressionParser.Process(element);
        }

        private string wikiText;
        private StringReader reader;
        private List<IWikiLineExpressionParser> lineExpressionParsers = new List<IWikiLineExpressionParser>();
        private List<IWikiInlineExpressionParser> inlineExpressionParsers = new List<IWikiInlineExpressionParser> ();
        private WikiTextElement rootElement = null;
        private static readonly ILog log = LogManager.GetLogger(typeof(WikiTextParser));
    }
}